package com.gframework.mybatis.util.generator.core.codegen;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.gframework.mybatis.util.generator.core.api.GeneratorConst;
import com.gframework.mybatis.util.generator.core.codegen.dom.java.Field;
import com.gframework.mybatis.util.generator.core.codegen.dom.java.FullyQualifiedJavaType;
import com.gframework.mybatis.util.generator.core.codegen.dom.java.JavaVisibility;
import com.gframework.mybatis.util.generator.core.codegen.dom.java.Method;
import com.gframework.mybatis.util.generator.core.codegen.dom.java.Parameter;
import com.gframework.mybatis.util.generator.core.codegen.dom.java.TopLevelClass;
import com.gframework.mybatis.util.generator.core.conf.Column;
import com.gframework.mybatis.util.generator.core.conf.Configuration;
import com.gframework.mybatis.util.generator.core.conf.DbFullName;
import com.gframework.mybatis.util.generator.core.conf.MetaData;
import com.gframework.mybatis.util.generator.core.interceptor.JavaBeanInterceptor;
import com.gframework.mybatis.util.generator.core.interceptor.JavaDaoInterceptor;
import com.gframework.mybatis.util.generator.core.util.JavaBeansUtil;
import com.gframework.mybatis.util.generator.core.util.StringUtility;

/**
 * JavaBean构造类，用于生成一个javabean的class.<br>
 * <p>JavaBean的生成目前除拦截器外再无其他任何扩展支持操作。</p>
 * <p>如果你想要设置bean创建拦截器，请参考{@link JavaBeanInterceptor}。</p>
 * <p>如果你想自定义sql方法，请参考{@link SqlOperatorGenerator}。</p>
 * <p>如果你想要设置dao接口创建拦截器，请参考{@link JavaDaoInterceptor}。</p>
 * 
 * @since 1.0.0
 * @author Ghwolf
 */
public class JavaBeanGenerator {

	/**
	 * 配置信息
	 */
	private Configuration config;

	public JavaBeanGenerator(Configuration config) {
		this.config = config;
	}

	/**
	 * 取得生成之后并经过格式化的一个完整的JavaBean实体类结构字符串.
	 * 
	 * @param table 要操作的表
	 * @return 返回生成的结构字符串，如果table为null，或因拦截器导致的table生成为null，则返回null
	 */
	public TopLevelClass createJavaBean(MetaData table) {
		if (table == null) return null;
		// 一些常量字段，放到类的最后
		List<Field> constField = new ArrayList<>();
		TopLevelClass cls = this.getBasicClass(table,constField);
		if (cls == null) return null;

		this.createFields(table, cls,constField);
		this.createMethods(table, cls,constField);
		
		for (Field f : constField) {
			cls.addField(f);
		}

		JavaBeanInterceptor inter = this.config.getJavaBeanInterceptor();
		return inter.afterCreate(table, cls, this.config);
	}
	
	/**
	 * 取得一个基本的类结构对象
	 * 
	 * @param table 表结构信息
	 * @return 返回类结构对象
	 */
	private TopLevelClass getBasicClass(MetaData table,List<Field> constField) {
		TopLevelClass cls = new TopLevelClass(this.config.getBasePackage() + "." + table.getModuleName() + ".entity.pojo." + table.getFormatTableName());
		cls.setVisibility(JavaVisibility.PUBLIC);

		String nowStr = LocalDate.now().toString();

		// 生成Table注解
		StringBuilder tabAnno ;
		Field constTableName = null ;
		if (this.config.isCreateNameConst()) {
			constTableName = this.createConstTableNameField(table.getTableName());
			constField.add(constTableName);
			tabAnno = new StringBuilder("@Table(name=" + cls.getType().getShortNameWithoutTypeArguments() + "." + constTableName.getName() + ",");
		} else {
			tabAnno = new StringBuilder("@Table(name=\"" + table.getTableName() + "\",");
		}
		if (this.config.isCreateCatalogAndSchema()) {
			if (!StringUtility.isEmpty(table.getCatalog())) {
				tabAnno.append("catalog=\"" + table.getCatalog() + "\",");
			}
			if (!StringUtility.isEmpty(table.getSchema())) {
				tabAnno.append("schema=\"" + table.getSchema() + "\",");
			}
		}
		tabAnno.delete(tabAnno.length() - 1, tabAnno.length()).append(")");
		cls.addAnnotation(tabAnno.toString());
		
		// 生成serialVersionUID
		Field serialVersionUID = new Field("serialVersionUID",new FullyQualifiedJavaType("long"));
		serialVersionUID.setVisibility(JavaVisibility.PRIVATE);
		serialVersionUID.setStatic(true);
		serialVersionUID.setFinal(true);
		serialVersionUID.setInitializationString((int)(table.hashCode()) + "L");
		cls.addField(serialVersionUID);

		// 生成注释
		cls.addJavaDocLine("/** ");
		if (constTableName == null) {
			cls.addJavaDocLine(" * " + table.getTableName() + "表对应的POJO实体类.<br>");
		} else {
			cls.addJavaDocLine(" * {@value #" + constTableName.getName() + "} 表对应的POJO实体类.<br>");
		}
		if (table.getTableComment().length() > 50) {
			String[] arr = StringUtility.splitByLength(table.getTableComment(), 50);
			for (int x = 0; x < arr.length; x ++) {
				if (x == 0) {
					cls.addJavaDocLine(" * <p>" + arr[x]);
				} else if (x == arr.length - 1) {
					cls.addJavaDocLine(" * " + arr[x] + "</p>");
				} else {
					cls.addJavaDocLine(" * " + arr[x]);
				} 
			}
		} else {
			cls.addJavaDocLine(" * " + table.getTableComment());
		}
		cls.addJavaDocLine(" *");
		cls.addJavaDocLine(" * <p>" + GeneratorConst.VERSION_REMARK + "</p>");
		cls.addJavaDocLine(" * @since " + nowStr);
		cls.addJavaDocLine(" * @author " + GeneratorConst.AUTHOR);
		cls.addJavaDocLine(" * @version " + GeneratorConst.VERSION);
		cls.addJavaDocLine(" */");

		cls.addImportedType("javax.persistence.*");
		cls.addImportedType("java.io.Serializable");

		JavaBeanInterceptor inter = this.config.getJavaBeanInterceptor();
		return inter == null ? cls : inter.createTable(table, cls, this.config);
	}

	/**
	 * 取得一个类中所有应当生成的属性对象.<br>
	 * 
	 * @param table 表结构信息
	 * @param cls javabean类结构对象
	 */
	private void createFields(MetaData table, TopLevelClass cls,List<Field> constField) {
		JavaBeanInterceptor inter = this.config.getJavaBeanInterceptor();

		// 筛选出忽略生成的列
		List<String> ignoreList = getIgnoreColumnList(table);

		for (Column col : table.getColumns()) {
			if (ignoreList.contains(col.getColumnName())) {
				continue;
			}

			Field f = new Field();
			Class<?> type = col.getJavaType();
			if (type.getPackage() != null && !"java.lang".equals(type.getPackage().getName())) {
				cls.addImportedType(type.getName());
			}
			f.setName(col.getFormatColumnName());
			f.setType(new FullyQualifiedJavaType(col.getJavaType().getSimpleName()));
			f.setVisibility(JavaVisibility.PRIVATE);

			String comment = StringUtility.isEmpty(col.getColumnComment()) ? "(none-javadoc)" : col.getColumnComment();

			// 生成注释
			f.addJavaDocLine("/**");
			f.addJavaDocLine(" * " + comment + ".");
			f.addJavaDocLine(" */");

			// 生成注解
			StringBuilder colAnno = new StringBuilder("@Column(");
			if (this.config.isCreateNameConst()) {
				Field cf = this.createConstColumNameField(col.getColumnName(), col.getFormatColumnName(),comment);
				constField.add(cf);
				colAnno.append("name=" + cf.getName() + ", ");
			} else {
				colAnno.append("name=\"" + col.getColumnName() + "\", ");
			}
			colAnno.append("nullable=" + col.isNullable());
//			colAnno.append("table=" + (this.config.isCreateNameConst() ? "TABLE_NAME" : "\"" + col.getTableName() + "\""));
			if (Number.class.isAssignableFrom(type) || type == BigDecimal.class || type == BigInteger.class) {
				// 数字类型
				if (col.getDecimalDigits() > 0) {
					colAnno.append(", precision=" + col.getColumnSize());
					colAnno.append(", scale=" + col.getDecimalDigits());
				} else {
					colAnno.append(", precision=" + col.getColumnSize());
				}
			} else if (type == String.class || type == Character.class) {
				// 字符串类型
				colAnno.append(", length=" + col.getColumnSize());
			} else {
			}
			colAnno.append(")");
			f.addAnnotationWithGetter(colAnno.toString());
			if (col.isPrimaryKey()) {
				f.addAnnotationWithGetter("@Id");
			}

			Field ff = inter == null ? f : inter.createField(table, cls, f, col, this.config);
			if (ff != null) {
				cls.addField(ff);
			}
		}
		
	}

	/**
	 * 取得一个类中所有应当生成的方法对象.<br>
	 * 
	 * @param table 表结构信息
	 * @param cls javabean类结构对象
	 */
	private void createMethods(MetaData table, TopLevelClass cls,List<Field> constField) {
		JavaBeanInterceptor inter = this.config.getJavaBeanInterceptor();

		// 添加无参构造方法
		Method constructor = new Method(table.getFormatTableName());
		constructor.setConstructor(true);
		constructor.addBodyLine("");
		constructor.setVisibility(JavaVisibility.PUBLIC);
		cls.addMethod(constructor);

		// 筛选出忽略生成的列
		List<String> ignoreList = getIgnoreColumnList(table);

		// 生成setter、getter
		for (Column col : table.getColumns()) {
			if (ignoreList.contains(col.getColumnName())) {
				continue;
			}

			Class<?> type = col.getJavaType();
			FullyQualifiedJavaType jt = new FullyQualifiedJavaType(type.getSimpleName());

			// setter
			Method setter = new Method("set" + JavaBeansUtil.getCamelCaseString(col.getColumnName(), true));
			setter.setVisibility(JavaVisibility.PUBLIC);
			setter.addParameter(new Parameter(jt, col.getFormatColumnName()));
			setter.addJavaDocLine("/**");
			if (StringUtility.isEmpty(col.getColumnComment())) {
				setter.addJavaDocLine(" * (none-javadoc).");
			} else {
				setter.addJavaDocLine(" * 设置 " + col.getColumnComment() + ".");
				setter.addJavaDocLine(" * @param " + col.getFormatColumnName() + " " + col.getColumnComment());
			}
			setter.addJavaDocLine(" */");
			setter.addBodyLine("this." + col.getFormatColumnName() + " = " + col.getFormatColumnName() + " ;");

			// getter
			Method getter = new Method("get" + JavaBeansUtil.getCamelCaseString(col.getColumnName(), true));
			getter.setReturnType(new FullyQualifiedJavaType(type.getSimpleName()));
			getter.setVisibility(JavaVisibility.PUBLIC);
			getter.setReturnType(jt);
			getter.addJavaDocLine("/**");
			if (StringUtility.isEmpty(col.getColumnComment())) {
				getter.addJavaDocLine(" * (none-javadoc).");
			} else {
				getter.addJavaDocLine(" * 取得 " + col.getColumnComment() + ".");
				getter.addJavaDocLine(" * @return " + col.getColumnComment());
			}
			getter.addJavaDocLine(" */");
			getter.addBodyLine("return this." + col.getFormatColumnName() + " ;");

			// 读取属性注解并放到getter上
			List<String> fieldAnnos = this.getFieldAnnotationsWithMethod(cls, JavaBeansUtil.getCamelCaseString(col.getColumnName(), false));
			for (String anno : fieldAnnos) {
				getter.addAnnotation(anno);
			}

			Method iS = inter == null ? setter : inter.createSetter(table, cls, setter, col, this.config);
			Method iG = inter == null ? getter : inter.createGetter(table, cls, getter, col, this.config);
			if (iS != null) {
				cls.addMethod(iS);
			}
			if (iG != null) {
				cls.addMethod(iG);
			}
		}

	}
	
	/**
	 * 取得一个类指定属性的所有注解集合，没有择机和长度为0
	 */
	private List<String> getFieldAnnotationsWithMethod(TopLevelClass cls,String fieldName) {
		for (Field f : cls.getFields()) {
			if (f.getName().equals(fieldName)) {
				return f.getAnnotationsWithGetter() == null ? Collections.emptyList() : f.getAnnotationsWithGetter();
			}
		}
		return Collections.emptyList();
	}

	/**
	 * 取得忽略的列
	 */
	private List<String> getIgnoreColumnList(MetaData table) {
		List<String> ignoreList = new ArrayList<>();
		Set<Map.Entry<DbFullName, List<String>>> ignoreColumn = this.config.getIgnoreColumns().entrySet();
		for (Map.Entry<DbFullName, List<String>> entry : ignoreColumn) {
			String catalog = entry.getKey().getCatalog();
			String schema = entry.getKey().getSchema();
			String nameReg = entry.getKey().getNameReg();
			boolean f1 = StringUtility.stringIsNullOrEquals(catalog, table.getCatalog());
			boolean f2 = StringUtility.stringIsNullOrEquals(schema, table.getSchema());
			boolean f3 = nameReg == null || table.getTableName().matches(nameReg);
			if (f1 && f2 && f3) {
				ignoreList.addAll(entry.getValue());
			}
		}
		return ignoreList;
	}
	/**
	 * 创建TABLE_NAME常量
	 */
	private Field createConstTableNameField(String tableName) {
		Field f = new Field();
		f.setVisibility(JavaVisibility.PUBLIC);
		f.setStatic(true);
		f.setFinal(true);
		f.setType(FullyQualifiedJavaType.getStringInstance());
		f.setName("TABLE_NAME");
		f.setInitializationString('"' + tableName + '"');
		f.addJavaDocLine("/** 当前POJO对应表名称：{@value} */");
		return f ;
	}
	/**
	 * 创建列名称常量
	 */
	private Field createConstColumNameField(String columnName,String fieldName,String comment) {
		Field f = new Field();
		f.setVisibility(JavaVisibility.PUBLIC);
		f.setStatic(true);
		f.setFinal(true);
		f.setType(FullyQualifiedJavaType.getStringInstance());
		f.setName(columnName.toUpperCase());
		f.setInitializationString('"' + columnName + '"');
		f.addJavaDocLine("/** " + fieldName + " 属性 对应的列名称：{@value}。 " + comment + " */");
		return f ;
	}
}
